var net = require("net");
module.exports = MCProtocol;

function MCProtocol(param) {
  let self = this;

  self.name = param.name || "";
  self.connectionTarget = {
    host: param.host || "192.168.30.1",
    port: param.port || 6200,
  };
  self.series = param.series || "Q";// or iQ
  //二进制传输报文,Q/L与iQ-R系列报文长度不同
  // self.transmissionFormate = "binary" || param.formate;
  self.logLevel = param.logLevel || 0;
  self.packetTimeout = param.timeout || 4000;

  // Network No.(00)   PC No.(FF)   Request destination module I/O No.(FF03)   module station No.(00)
  let stationInfo = param.stationInfo || '00FFFF0300';
  self.plcMsgInfo = "5000" + stationInfo

  let monitoringTime = parseInt(self.packetTimeout / 250, 10).toString(16);
  self.monitoringTime = AB2BA(padZero(monitoringTime, 4))

  self._socket = undefined;
  self.connectionState = 0;

  self.timeoutInfo = "";
  self._timeoutId = undefined;

  self.isReading = false;
  self.isWriting = false;

  self.currentCmd = ''
  self.currentReadType = ''
  self.currentAddr = ''
  // self.sendPacketArray = [];
  // self.currentOperateIndex = 0;
  //[{addr:d2,action:write/read,length:0,value:0},{addr:d2,action:write/read,value:12,length:0,value:0}]
}

MCProtocol.prototype.log = function (content, debugLevel) {
  let self = this,
    idtext;
  self.name ? (idtext = self.name) : (idtext = self.connectionTarget.host);
  if (self.logLevel >= debugLevel) {
    console.log("[" + idtext + " " + new Date().getTime() + "] " + content);
  }
};

MCProtocol.prototype.connect = function (callback) {
  let self = this;
  //需要判断当前连接状态
  if (self.connectionState >= 1) {
    return;
  }

  self.connectionState = 0;
  self.isReading = false;
  self.isWriting = false;
  if (typeof self._socket !== "undefined") {
    self._socket.removeAllListeners("data");
    self._socket.removeAllListeners("error");
    self._socket.removeAllListeners("close");
    self._socket.removeAllListeners("connect");
  }

  self._socket = new net.Socket();
  self._socket.setEncoding("hex");
  // 长连接,每1分钟空闲后发送连接确认
  self._socket.setKeepAlive(true, 60000);

  self._socket.connect(
    self.connectionTarget.port,
    self.connectionTarget.host,
    function () {
      //连接成功后
      clearTimeout(self._timeoutId);

      self.log(
        "The tcp connection to " +
        self.connectionTarget.host +
        " on port " +
        self.connectionTarget.port +
        " has been succeeded",
        0
      );
      self.connectionState = 4; // 4 = already connected

      self._socket.on("data", function () {
        //解析响应数据
        self.onResponse.apply(self, arguments);
      });

      callback && callback();

      //  03FF10006400000020440300341276980901  1E 83 00
      // self.sendRead("");
    }
  );

  self.connectionState = 1; // 1 = trying to connect

  // 初始化连接PLC超时,默认等待时间为通讯延时的5倍
  self.timeoutInfo = "Initial connection";
  self._timeoutId = setTimeout(function () {
    self.log(self.timeoutInfo + " timeout !!", 0);
    self._socket.end();
    self.connectionState = 0;
  }, self.packetTimeout * 5);

  //连接出错
  self._socket.on("error", function (e) {
    self.log("Error " + e.code + " occured when " + self.timeoutInfo || "", 0);
    self.connectionState = 0;
    self.isReading = false;
    self.isWriting = false;
    self._timeoutId && clearTimeout(self._timeoutId);
  });

  //连接断开
  self._socket.on("close", function () {
    self.log("Connection closed when " + self.timeoutInfo || "", 0);
    self.connectionState = 0;
    self.isReading = false;
    self.isWriting = false;
    self._timeoutId && clearTimeout(self._timeoutId);
  });

  self.log(
    "Initiating a new connection, attempting to connect to " +
    self.connectionTarget.host +
    " on port " +
    self.connectionTarget.port,
    0
  );
};

MCProtocol.prototype.read = function () {
  let self = this;
  if (self.connectionState != 4)
    return self.log('Not connected !', 0);
  if (self.isReading = true)
    return self.log('Is reading now, please try again later.', 0);
  if (self.isWriting = true)
    return self.log('Is wirting now, please try again later.', 0);

  if (arguments.length === 0) return self.log('读取参数不能为空', 0)
  if (arguments.length > 2) return self.log('读取参数传入有误', 0)

  let msgStr, addr, length, type,
    addr = arguments[0]
  type = self.getDeviceInfo(addr)
  type = type.substr(-1)
  if (!type) return self.log('读取不支持的数据类型', 0)

  if (arguments.length === 1) {
    if (type === 'B') msgStr = self.batchReadInBit(addr, 1)
    if (type === 'W') msgStr = self.batchReadInWord(addr, 1)
    if (type === 'D') msgStr = self.batchReadInWord(addr, 2)
  }

  if (arguments.length === 2) {
    length = arguments[1]
    let isNum = /^\d+$/
    if (isNum.test(length)) {
      if (type === 'B') msgStr = self.batchReadInBit(addr, length)
      if (type === 'W') msgStr = self.batchReadInWord(addr, length)
      if (type === 'D') msgStr = self.batchReadInWord(addr, length)
    } else { return self.log('读取的数据长度有误', 0) }
  }

  if (!msgStr) return self.log('无法获取有效读取信息报文', 0)
  self.currentReadType = type
  let sumCheck = Math.ceil(msgStr.length / 2) + 2
  sumCheck = AB2BA(padZero(parseInt(sumCheck, 10).toString(16), 4))
  msgStr = self.plcMsgInfo + sumCheck + self.monitoringTime + msgStr
  // 开始发报文
  self.isReading = true;
  self.log("Reading " + msgStr, 2);
  self._socket.write(str2buffer(msgStr));

  self.timeoutInfo = "Read";
  self._timeoutId = setTimeout(function () {
    self.log(self.timeoutInfo + " timed out !", 0);
    // self._socket.end();
    // self.connectionState = 0;
    self.isReading = false;
  }, self.packetTimeout)
};
MCProtocol.prototype.readBlock = function () {
  let self = this;
  if (self.connectionState != 4)
    return self.log('Not connected !', 0);
  if (self.isReading = true)
    return self.log('Is reading now, please try again later.', 0);
  if (self.isWriting = true)
    return self.log('Is wirting now, please try again later.', 0);

  if (arguments.length === 0) return self.log('读取参数不能为空', 0)
  if (arguments.length > 1) return self.log('读取参数传入有误', 0)

  let msgStr, addr, blocks, valid, type, reg = /^([A-Z]+-[A-Fa-f0-9]+)-(\d+)$/
  blocks = arguments[0]

  if (Array.isArray(blocks)) {
    blocks.forEach(function (el) {
      if (!reg.test(el)) valid = false
      addr = RegExp.$1
      type = self.getDeviceInfo(addr)
      type = type.substr(-1)
      if (!type) return valid = false
    })
    if (!valid) return self.log('读取的数据格式错误或不支持的数据类型', 0)
  } else {
    if (!reg.test(blocks)) { return self.log('读取不支持的数据类型', 0) }
    addr = RegExp.$1
    type = self.getDeviceInfo(addr)
    type = type.substr(-1)
    if (!type) return self.log('读取不支持的数据类型', 0)
  }

  msgStr = self.readMultiple(blocks)
  if (!msgStr) return self.log('无法获取有效读取信息报文', 0)
  let sumCheck = Math.ceil(msgStr.length / 2) + 2
  sumCheck = AB2BA(padZero(parseInt(sumCheck, 10).toString(16), 4))
  msgStr = self.plcMsgInfo + sumCheck + self.monitoringTime + msgStr
  // 开始发报文
  self.isReading = true;
  self.log("Reading " + msgStr, 2);
  self._socket.write(str2buffer(msgStr));

  self.timeoutInfo = "Read";
  self._timeoutId = setTimeout(function () {
    self.log(self.timeoutInfo + " timed out !", 0);
    // self._socket.end();
    // self.connectionState = 0;
    self.isReading = false;
  }, self.packetTimeout)
};

MCProtocol.prototype.write = function () {
  let self = this;
  if (self.connectionState != 4)
    return self.log('Not connected !', 0);
  if (self.isReading = true)
    return self.log('Is reading now, please try again later.', 0);
  if (self.isWriting = true)
    return self.log('Is wirting now, please try again later.', 0);

  if (arguments.length !== 2) return self.log('写入参数传入有误', 0)

  let msgStr, type, addr = arguments[0]
  let writeData = arguments[1]

  if (!Array.isArray(writeData))
    writeData = [].push(writeData)

  if (!Array.isArray(addr)) {
    type = getDeviceType(addr)
    if (!type) return self.log('写入不支持的数据类型', 0)

    if (type === 'B') {
      let reg = /^[01]$/
      if (writeData.every(val => reg.test(val))) {
        writeData = writeData.map(val => padZero(val, 2))
        msgStr = batchWriteInBit(addr, writeData)
      } else {
        return self.log('写入的数值有误', 0)
      }
    }

    if (type === 'W') {
      let reg = /^[A-Fa-f0-9]{4}$/
      writeData = writeData.map(val => padZero(val, 4))

      if (writeData.every(val => reg.test(val))) {
        msgStr = batchWriteInWord(addr, writeData)
      } else {
        return self.log('写入的数值有误', 0)
      }
    }
  } else {
    if (addr.length != writeData.length)
      return self.log('写入的数据长度不一致', 0)

    let isAllBit = addr.every(el => getDeviceType(el) === 'B')
    let isAllWord = addr.every(el => getDeviceType(el) === 'W')
    if (!isAllBit && !isAllWord)
      return self.log('写入的数据类型不支持或不一致', 0)

    if (isAllBit) {
      let reg = /^[01]$/
      if (writeData.every(val => reg.test(val))) {
        writeData = writeData.map(val => padZero(val, 2))
        msgStr = randomWriteInBit(addr, writeData)
      } else {
        return self.log('写入的数值有误', 0)
      }
    }

    if (isAllWord) {
      let reg = /^[A-Fa-f0-9]{4}$/
      writeData = writeData.map(val => padZero(val, 4))

      if (writeData.every(val => reg.test(val))) {
        msgStr = randomWriteInWord(addr, writeData)
      } else {
        return self.log('写入的数值有误', 0)
      }
    }
  }

  if (!msgStr) return self.log('无法获取读取信息报文', 0)
  // 开始发报文
  self.isWriting = true;
  self._socket.write(str2buffer(msgStr));
  self.log("Writing " + msgStr, 2);

  self.timeoutInfo = "Write";
  self._timeoutId = setTimeout(function () {
    self.log(self.timeoutInfo + " timed out !", 0);
    // self._socket.end();
    // self.connectionState = 0;
    self.isWriting = false;
  }, self.packetTimeout)
};
MCProtocol.prototype.writeBlock = function () { }

MCProtocol.prototype.onResponse = function (resData) {
  let self = this;
  let data = resData;

  if (data.length < 2) {
    self.log("DATA LESS THAN 2 BYTES RECEIVED.  NO PROCESSING WILL OCCUR", 0);
    self.log(data, 1);
    return null;
  }

  if (!self.isReading && !self.isWriting) {
    self.log("Unexpected packet arrived, it wasn't a write or read reply", 0);
    self.log(data, 1);
    return null;
  }


  self.log("Received " + data.length + " bytes of data from PLC.", 1);

  if (self.isReading) {
    self.log("Parsing read response", 1);
    self.parseReadResponse(data);
  }

  if (self.isWriting) {
    self.log("Parsing write response", 1);
    self.parseWriteResponse(data);
  }

};

MCProtocol.prototype.parseReadResponse = function (data) {
  let self = this
  clearTimeout(self._timeoutId);
  self.isReading = false;
  //D00000FFFF030006000000 34 12 02 00
  self.log(data, 0);
  // endCode 
  // normal '00'
  // errorCode 58 = 数据错误, 56 = 设备代码错误
  // errorCode + abnormalCode '5B' + '10'
  if (self.currentCmd === '00') {
    // batchReadInBit
    if (data.startsWith("8000"))
      return self.log(self.currentAddr + ': ' + data.slice(4).split(''), 0)
  }
  if (self.currentCmd === '01') {
    // batchReadInWord
    if (data.startsWith("8100"))
      return self.log(self.currentAddr + ': ' + str2wordArray(data.slice(4)), 0)
  }

  return self.log(self.currentAddr + '读取失败', 0)
};

MCProtocol.prototype.parseWriteResponse = function (data) {
  let self = this
  clearTimeout(self._timeoutId);
  self.isWriting = false;
  //D00000FFFF030002000000
  self.log(data, 0);
  // endCode 
  // normal '00'
  // errorCode 58 = 数据错误, 56 = 设备代码错误
  // errorCode + abnormalCode '5B' + '10'
  if (self.currentCmd === '02') {
    // batchWriteInBit
    if (data === '8200') return self.log(self.currentAddr + '写入成功', 0)
  }
  if (self.currentCmd === '03') {
    // batchWriteInWord
    if (data === '8300') return self.log(self.currentAddr + '写入成功', 0)

  }
  if (self.currentCmd === '04') {
    // randomWriteInBit
    if (data === '8400') return self.log(self.currentAddr + '写入成功', 0)

  }
  if (self.currentCmd === '05') {
    // randomWriteInWord
    if (data === '8500') return self.log(self.currentAddr + '写入成功', 0)

  }

  return self.log(self.currentAddr + '写入失败', 0)
};


// batchWriteInWord 0114 sub 0000(Q) 0200(iQ)
//    0114 0000 64 00 00 90 02 00 + 47 23 96 AB      M100-M131
//    M107-M100(M104=1,M105=1,1+2^1=3)  M115-M108 M123-M116 M131-M124

//    0114 0000 64 00 00 A8 03 00 + 9519 0212 3011      D100=6549(10#)=1995(16#) D101=1202(16#)


// randomReadInWord 0304 sub 0000(Q) 0200(iQ)
// 0304 0000 04(wordPoints) 03(doubleWordPoints) +
//   00 00 00 A8 deviceNum deviceCode * 4  +
//   DC 05 00 A8 deviceNum deviceCode * 3

//Subheader       Network No.   PC No.   Request destination module I/O No.   module station No.
// 50 00             00          FF                 FF03                          00  
// D0 00 
//Request data length(16)    onitoring timer (4S)  Command  Subcommand   Head device number(100)
//1000                        1000                  0114      0000         64 00 00
// SubCommand Q/L '0000' / iQ-R '0200'(binary)

//Device code(M)    Number of device points(2)       Write data
//90                0200                             4723  96AB
//  5000 00FF FF03 00 10 00 1000 0114 0000 640000 90 0200 4723 96AB

//读写Bit
MCProtocol.prototype.batchReadInBit = function (addr, length) {
  // batchReadInBit  0104 sub 0100(Q) 0300(iQ)
  //   0104 0100 64 00 00 90 08 00    M100-M107
  //   0001 0011   M100 = 0, M107 = 1
  let self = this, deviceInfo, points, subCommand, command = "0104"

  self.currentCmd = 'batchReadInBit', self.currentAddr = addr

  subCommand = (self.series === 'Q' ? '0100' : '0300')
  deviceInfo = self.getDeviceInfo(addr)
  deviceInfo = substr(0, deviceInfo.length - 1)
  points = parseInt(length, 10).toString(16)
  points = AB2BA(padZero(points, 4))

  return + command + subCommand + deviceInfo + points
}
MCProtocol.prototype.batchWriteInBit = function (addr, bitArray) {
  // batchWriteInBit 0114 sub 0100(Q) 0300(iQ)
  //    0114 0100 64 00 00 90 08 00 + 11 00 11 00 M100=1, M107=0
  if (!Array.isArray(bitArray)) bitArray = [].push(bitArray)
  let self = this, deviceInfo, points, subCommand, command = "0114"

  self.currentCmd = 'batchWriteInBit', self.currentAddr = addr

  subCommand = (self.series === 'Q' ? '0100' : '0300')
  deviceInfo = self.getDeviceInfo(addr)
  deviceInfo = substr(0, deviceInfo.length - 1)
  points = parseInt(bitArray.length, 10).toString(16)
  points = AB2BA(padZero(points, 4))

  let writeData = bitArray.join('')

  return + command + subCommand + deviceInfo + points + writeData
};

//读word  D-1F-2
MCProtocol.prototype.readMultiple = function (blockArray) {
  // batchReadMultiple 0604 sub 0000(Q) 0200(iQ)
  //    0604 0000 02(wordBlocks) 03(bitBlocks ) + 
  //        00 00 00 A8 04 00       deviceNum deviceCode points(1=16bit) + ...
  if (!Array.isArray(blockArray)) blockArray = [].push(blockArray)
  let self = this, points, subCommand, command = "0604"

  self.currentCmd = 'readMultiple', self.currentAddr = blockArray

  subCommand = (self.series === 'Q' ? '0000' : '0200')
  points = parseInt(blockArray.length, 10).toString(16)
  // only wordBlocks
  points = padZero(points, 2) + '00'
  let blockInfo = ''
  self.currentReadType = ''
  self.currentAddr = ''
  blockArray.forEach(addr => {
    let deviceInfo, len, reg = /^([A-Z]+-[A-Fa-f0-9]+)-(\d+)$/
    if (reg.test(addr)) {
      addr = RegExp.$1;
      len = RegExp.$2;

      deviceInfo = self.getDeviceInfo(addr)
      self.currentReadType += deviceInfo.substr(-1)
      self.currentAddr += addr

      deviceInfo = substr(0, deviceInfo.length - 1)
      len = parseInt(len, 10).toString(16)
      len = AB2BA(padZero(points, 4))

      blockInfo += deviceInfo + len
    }
  });

  return + command + subCommand + points + blockInfo
};
//写word  D-1F-2, [[0f11,01f]]
MCProtocol.prototype.writeMultiple = function (blockArray, dataArray) {
  // batchWriteMultiple 0614 sub 0000(Q) 0200(iQ)
  //        0614 0000 02(wordBlocks) 03(bitBlocks ) + 
  //            00 00 00 A8 04 00 + writeData(D0 D1 D2 D3) deviceNum deviceCode points(1=16bit) + ...
  //            00 00 00 90 02 00 + writeData (M15-M0, M31-M16)
  if (!Array.isArray(blockArray)) blockArray = [].push(blockArray)
  let self = this, points, subCommand, command = "0614"

  self.currentCmd = 'writeMultiple', self.currentAddr = blockArray

  subCommand = (self.series === 'Q' ? '0000' : '0200')
  points = parseInt(blockArray.length, 10).toString(16)
  // only wordBlocks
  points = padZero(points, 2) + '00'
  let blockData = ''
  blockArray.forEach((addr, i) => {
    let deviceInfo, len, writeData, reg = /^([A-Z]+-[A-Fa-f0-9]+)-(\d+)$/
    if (reg.test(addr)) {
      addr = RegExp.$1;
      len = RegExp.$2;

      deviceInfo = self.getDeviceInfo(addr)
      deviceInfo = substr(0, deviceInfo.length - 1)

      writeData = dataArray[i]
      if (Array.isArray(writeData)) {
        writeData = writeData.map(val => AB2BA(padZero(val, 4))).join('')
      } else {
        writeData = padZero(writeData, len * 4)
        str2wordArray(writeData).reverse().map(AB2BA).join('')
      }

      len = parseInt(len, 10).toString(16)
      len = AB2BA(padZero(points, 4))

      blockData += deviceInfo + len + writeData
    }
  });

  return + command + subCommand + points + blockData
};

//读写特殊长继电器
// LTS,LSTS, LTC,LSTC, LCS,LCC  LTN, LSTN, LCN 读word
MCProtocol.prototype.batchReadInWord = function (addr, length) {
  // batchReadInWord 0104 sub 0000(Q) 0200(iQ)
  //   0104 0000 64 00 00 90 02 00                    M100-M131
  //   34 12  02 00        M107-M100(M104=1,M105=1,1+2^1=3)  M115-M108 M123-M116 M131-M124

  //   0104 0000 64 00 00 C2 03 00             T100-T102
  //   34 12 02 00                             EF1D T100=1234(16#)=4660(10#)
  let self = this, deviceInfo, points, subCommand, command = "0104"

  self.currentCmd = 'batchReadInWord', self.currentAddr = addr

  subCommand = (self.series === 'Q' ? '0000' : '0200')
  deviceInfo = self.getDeviceInfo(addr)
  deviceInfo = substr(0, deviceInfo.length - 1)
  points = parseInt(length, 10).toString(16)
  points = AB2BA(padZero(points, 4))

  return + command + subCommand + deviceInfo + points
};
// LTS,LSTS, LTC,LSTC, LCS,LCC  同时写多个bit
MCProtocol.prototype.randomWriteInBit = function (addrArray, bitArray) {
  // randomWriteInBit 0214 sub 0100(Q) 0300(iQ)
  //     0214 0100 02(points)   32 00 00 90 00(OFF) + 2F 00 00 9D 01(ON)
  if (!Array.isArray(addrArray)) bitArray = [].push(addrArray)
  if (!Array.isArray(bitArray)) bitArray = [].push(bitArray)
  let self = this, points, subCommand, command = "0214"

  self.currentCmd = 'randomWriteInBit', self.currentAddr = addr

  subCommand = (self.series === 'Q' ? '0100' : '0300')

  points = parseInt(addrArray.length, 10).toString(16)
  points = AB2BA(padZero(points, 4))

  let writeData = ''
  addrArray.forEach((addr, i) => {
    let deviceInfo = self.getDeviceInfo(addr)
    deviceInfo = substr(0, deviceInfo.length - 1)
    writeData += deviceInfo + padZero(bitArray[i], 2)
  });

  return + command + subCommand + points + writeData
};
// LTN, LSTN, LCN 写doubleWord
MCProtocol.prototype.randomWriteInWord = function (addrArray, doubleWordArray) {
  // randomWriteInWord 0214 sub 0000(Q) 0200(iQ)
  //   0214 0000 04(wordPoints) 03(doubleWordPoints) +
  //     00 00 00 A8 deviceNum deviceCode writeData * 4  +
  //     DC 05 00 A8 deviceNum deviceCode writeData * 3  
  if (!Array.isArray(addrArray)) bitArray = [].push(addrArray)
  if (!Array.isArray(doubleWordArray)) bitArray = [].push(doubleWordArray)
  let self = this, points, subCommand, command = "0214"

  self.currentCmd = 'randomWriteInWord', self.currentAddr = addr

  subCommand = (self.series === 'Q' ? '0000' : '0200')

  points = parseInt(addrArray.length, 10).toString(16)
  // only doubleWord
  points = '00' + padZero(points, 2)

  let writeData = ''
  addrArray.forEach((addr, i) => {
    let deviceInfo = self.getDeviceInfo(addr)
    deviceInfo = substr(0, deviceInfo.length - 1)
    writeData += deviceInfo + str2wordArray(padZero(doubleWordArray[i], 8)).reverse().map(AB2BA).join('')
  });

  return + command + subCommand + points + writeData
};


// 地址格式 为M-10，X-FF这种， 编号-地址（16#）
MCProtocol.prototype.getDeviceInfo = function (addr) {
  let reg = /^([A-Z]+)-([A-Fa-f0-9]+)$/, deviceType, deviceAddr, deviceCode;

  if (reg.test(addr)) {
    deviceType = RegExp.$1;
    deviceAddr = RegExp.$2;
  } else {
    return null; // 地址格式错误
  }

  switch (deviceType) {
    case "X":
      deviceCode = "9C" + "B"; break
    case "Y":
      deviceCode = "9D" + "B"; break
    case "S":
      deviceCode = "98" + "B"; break
    case "DX":
      deviceCode = "A2" + "B"; break
    case "DY":
      deviceCode = "A3" + "B"; break
    case "M":
      deviceCode = "90" + "B"; break
    case "SM":
      deviceCode = "91" + "B"; break
    case "L":
      deviceCode = "92" + "B"; break
    case "F":
      deviceCode = "93" + "B"; break
    case "V":
      deviceCode = "94" + "B"; break
    case "B":
      deviceCode = "A0" + "B"; break
    case "SB":
      deviceCode = "A1" + "B"; break

    case "D":
      deviceCode = "A8" + "W"; break
    case "SD":
      deviceCode = "A9" + "W"; break
    case "RD":
      deviceCode = "2C" + "W"; break
    case "W":
      deviceCode = "B4" + "W"; break
    case "SW":
      deviceCode = "B5" + "W"; break
    case "R":
      deviceCode = "AF" + "W"; break
    case "ZR":
      deviceCode = "B0" + "W"; break
    case "Z":
      deviceCode = "CC" + "W"; break

    case "LZ":
      deviceCode = "62" + "D"; break

    case "TS":
      deviceCode = "C1" + "B"; break
    case "TC":
      deviceCode = "C0" + "B"; break
    case "TN":
      deviceCode = "C2" + "W"; break

    case "LTS":
      deviceCode = "51" + "B"; break
    case "LTC":
      deviceCode = "50" + "B"; break
    case "LTN":
      deviceCode = "52" + "D"; break

    case "STS":
      deviceCode = "C7" + "B"; break
    case "STC":
      deviceCode = "C6" + "B"; break
    case "STN":
      deviceCode = "C8" + "W"; break

    case "LSTS":
      deviceCode = "59" + "B"; break
    case "LSTC":
      deviceCode = "58" + "B"; break
    case "LSTN":
      deviceCode = "5A" + "D"; break

    case "CS":
      deviceCode = "C4" + "B"; break
    case "CC":
      deviceCode = "C3" + "B"; break
    case "CN":
      deviceCode = "C5" + "W"; break

    case "LCS":
      deviceCode = "55" + "B"; break
    case "LCC":
      deviceCode = "54" + "B"; break
    case "LCN":
      deviceCode = "56" + "D"; break

    default:
      return null;
  }

  //X1234  34 12 00 9C || 34 12 00 00 9C 00
  if (self.series !== 'Q') deviceCode = deviceCode.substr(0, 2) + '00' + deviceCode.substr(2, 1)

  if (self.series === 'Q') {
    // Q/L series
    deviceAddr = str2byteArray(padZero(deviceAddr, 6)).reverse().join('')
  } else {
    // iQ-R series
    deviceAddr = str2byteArray(padZero(deviceAddr, 8)).reverse().join('')
  }
  return deviceAddr + deviceCode
}


function str2buffer(str) {
  return new Buffer.from(str, "hex");
}

function str2wordArray(str) {
  let arr = [];
  for (let i = 0; i < str.length; i += 4) {
    let subStr = str.substr(i, 4);
    arr.push(subStr);
  }
  return arr;
}

function str2byteArray(str) {
  let arr = [];
  for (let i = 0; i < str.length; i += 2) {
    let subStr = str.substr(i, 2);
    arr.push(subStr);
  }
  return arr;
}

function padZero(str, len) {
  let zeroNum = len - str.toString().length;
  while (zeroNum--) {
    str = '0' + str
  }
  return str;
}

function AB2BA(str) {
  let a = str.substr(0, str.length / 2)
  let b = str.substr(str.length / 2)
  return b + a
}

//American Standard Code for Information Interchange
//16进制数41（十进制65）对应大写字母A占，一个字符占8位
//比如D10 16# 为4241，对应内容为16#BA,三菱软件显示字符为AB
function hex2ascii(value) {
  if (!value) return null;
  let strArr = str2byteArray(value).map((val) =>
    String.fromCharCode(parseInt(val, 16))
  );
  return strArr.join("");
};

//常用16位2进制表示的整数，最高位表示符号，0为正，1为负数
function hex2int16(value) {
  if (!value) return null;

  var b = new ArrayBuffer(2); //2*8 16位
  var a = new Uint8ClampedArray(b);
  // 将'01b0'这种拆分成'01' 'b0'高低换位后放进ArrayBuffer去再取对应格式出来
  str2byteArray(value)
    .reverse()
    .map((val, index) => (a[index] = parseInt(val, 16)));

  return new Int16Array(b)[0];
};
//常用16位2进制表示的整数，最高位为1表示2的15次方
function hex2uint16(value) {
  if (!value) return null;

  var b = new ArrayBuffer(2); //2*8 16位
  var a = new Uint8ClampedArray(b);
  str2byteArray(value)
    .reverse()
    .map((val, index) => (a[index] = parseInt(val, 16)));

  return new Uint16Array(b)[0];
};
//常用32位2进制表示的整数，最高位表示符号，0为正，1为负数
function hex2int32(value) {
  if (!value) return null;

  var b = new ArrayBuffer(4); //4*8 32位
  var a = new Uint8ClampedArray(b);
  str2byteArray(value)
    .reverse()
    .map((val, index) => (a[index] = parseInt(val, 16)));

  return new Int32Array(b)[0];
};
//常用32位2进制表示的整数，最高位为1表示2的31次方
function hex2uint32(value) {
  if (!value) return null;

  var b = new ArrayBuffer(4); //4*8 32位
  var a = new Uint8ClampedArray(b);
  str2byteArray(value)
    .reverse()
    .map((val, index) => (a[index] = parseInt(val, 16)));

  return new Uint32Array(b)[0];
};

//常用32位2进制表示的小数，最高位为1表示2的31次方
// _________________________________________________________
// 31        30              22                            0
// |符号位占1|   指数位占8      |         尾数部分占23       |
// _________________________________________________________

// 17.625在内存中的存储

// 首先要把17.625换算成二进制：10001.101  即 2^5 2^0 . 2^-1 2^-3

// 将10001.101右移，直到小数点前只剩1位：1.0001101 * 2^4 因为右移动了四位。

// 底数：因为小数点前必为1，所以IEEE规定只记录小数点后的就好。所以，此处的底数为：0001101
// 指数：实际为4，必须加上127(转出的时候，减去127)，所以为131。也就是10000011
// 指数占8位，表示0-255之间的数值，为方便判断指数正负，我们就要用一个中间值127来辅助
// 比如，8位的指数在内存中存储的值是125，然后读取的时候125-127得到-2，这个-2就是真实的指数值
// 通过这种方式就可以方便存储数值为-127 ~ 128的指数

// 符号部分是正数，所以是0
// 综上所述，17.625在内存中的存储格式是：符号, 10即E的多少次方, 小数1.后面部分
// +   131(4+127)  1. 0001101
// 0    10000011      0001101 00000000 00000000

function hex2float32(value) {
  if (!value) return null;

  var b = new ArrayBuffer(4); //4*8 32位
  var a = new Uint8ClampedArray(b);
  str2byteArray(value)
    .reverse()
    .map((val, index) => (a[index] = parseInt(val, 16)));

  return new Float32Array(b)[0];
};
//常用64位2进制表示的小数，最高位为1表示2的31次方
// _________________________________________________________
// 63        62              51                            0
// |符号位占1|   指数位占11      |         尾数部分占52       |
// _________________________________________________________

// 17.625双精度在内存中的存储格式类似，区别是指数基数为1023的移码
// +   1027(4+1023)  1. 0001101
// PLC编程尽量使用整数，不推荐使用浮点数
function hex2float64(value) {
  if (!value) return null;

  var b = new ArrayBuffer(8); //8*8 64位
  var a = new Uint8ClampedArray(b);
  str2byteArray(value)
    .reverse()
    .map((val, index) => (a[index] = parseInt(val, 16)));

  return new Float64Array(b)[0];
};

function hex2bitStr(hex) {
  switch (hex.toString().toUpperCase()) {
    case '0': return '0000'
    case '1': return '0001'
    case '2': return '0010'
    case '3': return '0011'
    case '4': return '0100'
    case '5': return '0101'
    case '6': return '0110'
    case '7': return '0111'
    case '8': return '1000'
    case '9': return '1001'
    case 'A': return '1010'
    case 'B': return '1011'
    case 'C': return '1100'
    case 'D': return '1101'
    case 'E': return '1110'
    case 'F': return '1111'
    default: return ''
  }
}
function bitStr2hex(str) {
  switch (str) {
    case '0000': return '0'
    case '0001': return '1'
    case '0010': return '2'
    case '0011': return '3'
    case '0100': return '4'
    case '0101': return '5'
    case '0110': return '6'
    case '0111': return '7'
    case '1000': return '8'
    case '1001': return '9'
    case '1010': return 'A'
    case '1011': return 'B'
    case '1100': return 'C'
    case '1101': return 'D'
    case '1110': return 'E'
    case '1111': return 'F'
    default: return ''
  }
}